package com.example.provider.aspect;

import com.example.api.common.annotation.LogTag;
import com.example.api.common.log.LogCollector;
import com.example.api.common.log.LogEvent;
import com.example.api.common.trace.TraceIdUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;

import java.net.InetAddress;
import java.time.LocalDateTime;
import java.util.Optional;

import com.fasterxml.jackson.databind.ObjectMapper;

@Slf4j
@Aspect
//@Component
public class LogAspect {

    @Autowired
    private LogCollector logCollector;

    @Value("${spring.application.name}")
    private String applicationName;

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Pointcut("@annotation(LogTag)")
    public void logPointcut() {
    }

    @Around("logPointcut()")
    public Object around(ProceedingJoinPoint point, LogTag logAnnotation) throws Throwable {
        long startTime = System.currentTimeMillis();
        String methodName = point.getSignature().getName();
        String className = point.getTarget().getClass().getName();
        String traceId = TraceIdUtil.getTraceId();
        // 创建日志事件
        Optional<LogEvent> eventOptional = createLogEvent(logAnnotation, point, traceId);

        try {
            // 打印请求日志
            logRequest(traceId, className, methodName, point.getArgs());

            // 执行业务逻辑
            Object result = point.proceed();

            // 打印响应日志
            logResponse(traceId, className, methodName, result);

            eventOptional.ifPresent(logEvent -> logEvent.setResponse(result));
            return result;
        } catch (Exception e) {
            // 打印异常日志
            logException(traceId, className, methodName, e);
            // 记录错误信息
            eventOptional.ifPresent(logEvent -> logEvent.setThrowable(e));
            throw e;
        } finally {
            eventOptional.ifPresent(event -> {
                event.setExecutionTime(System.currentTimeMillis() - startTime);
                logCollector.collect(event);
            });
        }
    }

    private void logRequest(String traceId, String className, String methodName, Object[] args) {
        try {
            String requestJson = objectMapper.writeValueAsString(args);
            log.info("[{}] Request - {}.{} - Args: {}", traceId, className, methodName, requestJson);
        } catch (Exception e) {
            log.error("[{}] Failed to serialize request args", traceId, e);
        }
    }

    private void logResponse(String traceId, String className, String methodName, Object result) {
        try {
            String responseJson = objectMapper.writeValueAsString(result);
            log.info("[{}] Response - {}.{} - Result: {}", traceId, className, methodName, responseJson);
        } catch (Exception e) {
            log.error("[{}] Failed to serialize response result", traceId, e);
        }
    }

    private void logException(String traceId, String className, String methodName, Exception e) {
        log.error("[{}] Exception - {}.{} - Error: {}", traceId, className, methodName, e.getMessage(), e);
    }

    private Optional<LogEvent> createLogEvent(LogTag logAnnotation, ProceedingJoinPoint point, String traceId) {
        if (logAnnotation.structured()) {
            return Optional.of(LogEvent.builder()
                    .traceId(traceId)
                    .serviceName(applicationName)
                    .ip(getLocalIp())
                    .threadName(Thread.currentThread().getName())
                    .operationName(logAnnotation.value())
                    .className(point.getTarget().getClass().getName())
                    .methodName(point.getSignature().getName())
                    .request(point.getArgs())
                    .timestamp(LocalDateTime.now())
                    .build());
        } else {
            return Optional.empty();
        }
    }

    private String getLocalIp() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (Exception e) {
            log.error("Failed to get local IP", e);
            return "unknown";
        }
    }
}